#pragma once // 防止头文件被重复包含

#include <iostream>
#include <fstream> //C++方式打开文件的头文件
#include <cstdio>
#include <string>
#include <ctime> //时间
#include <sys/types.h>
#include <cstdarg>       //va_list   解析可变模板参数
#include <unistd.h>      //sleep
#include <pthread.h>     //上锁线程库
#include "LockGuard.hpp" //上锁自己写的C++方式

bool gIsSave = false;                  // 默认false，打印Screen上， 不保存
const std::string logname = "log.txt"; // 默认保存至当前路径下的log.txt文件里

// 1. 日志是有等级的
enum Level
{
    DEBUG = 0,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

std::string LevelToString(int level)
{
    switch (level)
    {
    case DEBUG:
        return "Debug";
    case INFO:
        return "Info";
    case WARNING:
        return "Warning";
    case ERROR:
        return "Error";
    case FATAL:
        return "Fatal";
    }
    return "None";
}

// 获取当前时间
std::string GetTimeString()
{
    time_t curr_time = time(nullptr);               // 获取当前时间的时间戳，time_t其实是个无符号长整形
    struct tm *format_time = localtime(&curr_time); // 将时间戳转换为年月日等时间属性保存到struct tm结构体的各个成员里面
    if (format_time == nullptr)
        return "None"; // 调用失败，返回nullptr，没有时间
    // 调用成功，返回struct tm* 指针
    char time_buffer[1024];
    snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",
             format_time->tm_year + 1900,
             format_time->tm_mon + 1,
             format_time->tm_mday,
             format_time->tm_hour,
             format_time->tm_min,
             format_time->tm_sec);
    return time_buffer;
}

// 写入文件，以app方式打开，C++方式写入文件
void SaveFile(const std::string &filename, const std::string &message)
{
    std::ofstream out(filename, std::ios::app); // C++以对象构造方式以参数形式，std::ios::app方式打开指定filename路径下的文件
    if (!out.is_open())
        return;
    out << message; // 写
    out.close();    // 关闭文件
}

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
// 2. 日志是有格式的
//  日志等级 时间  文件名/行数--->准确定位到错误信息和错误位置

void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...) // printf里面也有，参数包，可变模板参数
{
    std::string levelstr = LevelToString(level); // 根据错误等级，输出错误信息
    std::string timestr = GetTimeString();
    pid_t selfid = getpid(); // 获取日志所在进程的pid

    // 解析可变模板参数 va_list arg   va_start  va_end vsnprintf
    char buffer[1024];
    va_list arg;           // va_list就是一个void*类型，arg就是声明出来的一个空类型指针罢了
    va_start(arg, format); // 让其指向format指向的第一个可变模板参数的地址
    // 开始逐个解析，用到vsnprintf
    vsnprintf(buffer, sizeof(buffer), format, arg);
    va_end(arg); // 将arg置为nullptr

    std::string message = "[" + timestr + "]" + "[" + levelstr + "]" + "[" + std::to_string(selfid) + "]" + "[" + filename + "]" + "[" + std::to_string(line) + "]" + buffer + "\n";

    // pthread_mutex_lock(&lock);
    LockGuard lockguard(&lock); // 锁的构造上锁，析构解锁
    // 打印屏幕上或保存文件，等也是公共资源，需要上锁保护互斥起来
    if (!issave) // 输出Screen
    {
        std::cout << message;
    }
    else
    {
        SaveFile(logname, message);
    }
}

// C99新特性__VA_ARGS__
// ##可以保证可变模板参数为0个

#define LOG(level, format, ...)                                                \
    do                                                                         \
    {                                                                          \
        LogMessage(__FILE__, __LINE__, gIsSave, level, format, ##__VA_ARGS__); \
    } while (0)

#define EnableFile()    \
    do                  \
    {                   \
        gIsSave = true; \
    } while (0)
#define EnableScreen()   \
    do                   \
    {                    \
        gIsSave = false; \
    } while (0)



    